/*
*
*
*     文档创建：jjl, jjl@hzncc.com
*/


module packet(
            sys_clk         ,
            sys_reset_n     ,

            //报文处理端信号
            rxdv            ,
            rxd             ,
            txen            ,
            txd
        );

    input           sys_clk         ;
    input           sys_reset_n     ;

    //报文处理接口
    input           rxdv            ;
    input   [1:0]   rxd             ;
    output          txen            ;
    output  [1:0]   txd             ;

    
/*
*   在此对报文做64bit的缓冲，也就是32个clk。
*   当rxdv有效后，对缓冲区赋值为64{1},开始探测帧定界符，当定界符出现后
*   开始发送缓冲区内容，接收的内容追加到缓冲区末尾
*/

    reg [63:0]      packet_buffer   ;   //报文缓冲区
    
    reg             rxdv_r          ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)
        rxdv_r      <=      1'b0    ;
    else
        rxdv_r      <=      rxdv    ;
    
    //rxdv信号上升沿
    wire            rxdv_posedge    ;
    assign rxdv_posedge = (~rxdv_r) & rxdv  ;
    //帧定界符信号
    reg             sfd_r           ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)
        sfd_r       <=      1'b0    ;
    else if(rxdv_posedge)
        sfd_r       <=      1'b0    ;
    else if(~sfd_r & (rxd == 2'b11))
        sfd_r       <=      1'b1    ;


    //接收数据
    //注意bit顺序，对于8bit，接收顺序rxd[1:0]:d1d0, d3d2, d4d5, d7d6
    //在这里把位置变换一下，发送的时候需要把位置转换回来
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)
        packet_buffer   <=      64'h00      ;
    else if(rxdv_posedge)   //在rxdv上升沿对缓冲区置位
        packet_buffer   <=      {64{1'b1}}  ;
    else if(sfd_r & rxdv)
        //packet_buffer   <=  {packet_buffer[61:0], rxd[1], rxd[0]};
        packet_buffer   <=  {packet_buffer[61:0], rxd[0], rxd[1]};

    //接收计数，最大支持2048Bytes包，一个字节4个clk，所以有8192个clk
    reg     [12:0]      rxd_cnt_r   ;   
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)
        rxd_cnt_r       <=      13'h0000    ;
    else if(rxdv_posedge)   //在rxdv上升沿计数清零
        rxd_cnt_r       <=      13'h0000    ;
    else if(sfd_r & rxdv)
        rxd_cnt_r       <=      rxd_cnt_r + 13'h01; 

    //接收数据的CRC32计算
    wire    [31:0]  rcv_crc                 ;
    rmii_crc32 u_rcv_crc32(
        .phy_clk        (           sys_clk         ),
        .sys_reset_n    (           sys_reset_n     ),
        .crc_enable     (           sfd_r & rxdv    ),
        .data_in        (           rxd             ),
        .crc_out        (           rcv_crc         )
    );

    reg     rcv_crc_flag                    ;   //接收数据的CRC校验结果
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)
        rcv_crc_flag    <=      1'b0        ;
    else if(sfd_r & rxdv)
        rcv_crc_flag    <=      |rcv_crc    ; //校验正确所有结果为0
    else if(rxdv_posedge)
        rcv_crc_flag    <=      1'b0        ;

    //对packet_buffer数据排列, 每个字节bit0和bit7,b1和b6..对掉
    wire    [63:0]      pkt_buf             ;
    assign pkt_buf[3:0]     = {packet_buffer[4],packet_buffer[5],packet_buffer[6],packet_buffer[7]} ;
    assign pkt_buf[7:4]     = {packet_buffer[0],packet_buffer[1],packet_buffer[2],packet_buffer[3]} ;
    assign pkt_buf[11:8]    = {packet_buffer[12],packet_buffer[13],packet_buffer[14],packet_buffer[15]} ;
    assign pkt_buf[15:12]   = {packet_buffer[8],packet_buffer[9],packet_buffer[10],packet_buffer[11]} ;
    assign pkt_buf[19:16]   = {packet_buffer[20],packet_buffer[21],packet_buffer[22],packet_buffer[23]} ;
    assign pkt_buf[23:20]   = {packet_buffer[16],packet_buffer[17],packet_buffer[18],packet_buffer[19]} ;
    assign pkt_buf[27:24]   = {packet_buffer[28],packet_buffer[29],packet_buffer[30],packet_buffer[31]} ;
    assign pkt_buf[31:28]   = {packet_buffer[24],packet_buffer[25],packet_buffer[26],packet_buffer[27]} ;
    assign pkt_buf[35:32]   = {packet_buffer[36],packet_buffer[37],packet_buffer[38],packet_buffer[39]} ;
    assign pkt_buf[39:36]   = {packet_buffer[32],packet_buffer[33],packet_buffer[34],packet_buffer[35]} ;
    assign pkt_buf[43:40]   = {packet_buffer[44],packet_buffer[45],packet_buffer[46],packet_buffer[47]} ;
    assign pkt_buf[47:44]   = {packet_buffer[40],packet_buffer[41],packet_buffer[42],packet_buffer[43]} ;
    assign pkt_buf[51:48]   = {packet_buffer[52],packet_buffer[53],packet_buffer[54],packet_buffer[55]} ;
    assign pkt_buf[55:52]   = {packet_buffer[48],packet_buffer[49],packet_buffer[50],packet_buffer[51]} ;
    assign pkt_buf[59:56]   = {packet_buffer[60],packet_buffer[61],packet_buffer[62],packet_buffer[63]} ;
    assign pkt_buf[63:60]   = {packet_buffer[56],packet_buffer[57],packet_buffer[58],packet_buffer[59]} ;
    //把低2字节转换字节序,为后面逻辑提供
    wire    [15:0]  pkt_u16;
    assign pkt_u16 = {pkt_buf[7:0],pkt_buf[15:8]};
    //把低4字节转换字节序
    wire    [31:0]  pkt_u32;
    assign pkt_u32 = {pkt_buf[7:0],pkt_buf[15:8],pkt_buf[23:16],pkt_buf[31:24]};

    //EtherCAT帧标志， 0x88a4
    //88a4计数24(DMAC)+24(SMAC)+8(PROTO)
    //0x88a4字节逆序为0x1125
    reg     [15:0]      ecat_proto_r        ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)
        ecat_proto_r    <=      16'h00      ;
    else if(sfd_r & rxdv & (rxd_cnt_r == 13'd56))
        ecat_proto_r    <=      pkt_buf[15:0];
    else if(rxdv_posedge)
       ecat_proto_r     <=      16'h00      ;
    //标识此帧是否为ECAT帧
    wire ecat_proto_flag;
    assign ecat_proto_flag = (ecat_proto_r == 16'h88a4);

    //提取ECAT帧长度
    //帧长度计数24(DMAC)+24(SMAC)+8(PROTO)+8(FR_LEN)
    reg [10:0]  ecat_fr_len;
    reg [3:0]   ecat_fr_type;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        ecat_fr_type    <=      4'h0        ;
        ecat_fr_len     <=      11'h000     ;
    end else if(sfd_r & rxdv & (rxd_cnt_r == 13'd64)) begin
        ecat_fr_type    <=      pkt_u16[15:12];
        ecat_fr_len     <=      pkt_u16[10:0];
    end else if(rxdv_posedge) begin
        ecat_fr_type    <=      4'h0        ;
        ecat_fr_len     <=      11'h000     ;
    end

    //当ecat_proto_flag有效时，并且rxd_cnt_r=24(DMAC)+24(SMAC)+8(PROTO)+8(FR_hdr)进入子报文处理
    //子报文使用单独计数器
    reg     [12:0]      sp_cnt_r            ;
    wire                sp_cnt_start        ;   //子报文计数开始标志
    wire                sp_cnt_add          ;   //子报文计数加一条件
    wire                sp_cnt_reset        ;   //子报文计数复位标志
    assign  sp_cnt_start = ecat_proto_flag & (rxd_cnt_r > 13'd64);
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_cnt_r    <=          13'h000     ;
    end else if(sp_cnt_start) begin //计数开始条件
        if(sp_cnt_reset) begin
            sp_cnt_r    <=      13'h000     ;
        end else if(sp_cnt_add)//子报文加一，当没有后续报文计数停止
            sp_cnt_r    <=          sp_cnt_r + 13'h001;
    end else begin  //新一帧数据开始时候，复位子报文计数器
        sp_cnt_r        <=      13'h000     ;
    end

    //子报文计数器为8时刻，读取命令和索引
    reg [7:0]   sp_cmd_r    ;
    reg [7:0]   sp_ind_r    ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_cmd_r    <=          8'h00     ;
        sp_ind_r    <=          8'h00     ;
    end else if(sp_cnt_r == 13'd7) begin
        sp_cmd_r    <=          pkt_buf[15:8]   ;
        sp_ind_r    <=          pkt_buf[7:0]    ;
    end

    //子报文计数为24时刻，读取地址
    reg [31:0]  sp_addr_r   ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_addr_r   <=          32'h00  ;
    end else if(sp_cnt_r == 13'd23) begin
        sp_addr_r   <=          pkt_u32[31:0];//pkt_buf[31:0]   ;
    end
    //子报文地址分为站点地址和和站内偏移
    wire    [15:0]  sp_adp          ;   //站点地址
    wire    [15:0]  sp_ado          ;   //站内地址
    assign sp_adp = sp_addr_r[15:0];
    assign sp_ado = sp_addr_r[31:16];

    //子报文计数为32时刻读取长度和后续报文标志
    reg             sp_nxt_r        ;   //为1表示还有后续子报文
    reg     [10:0]  sp_len_r        ;   //子报文长度
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_nxt_r    <=          1'b0  ;
        sp_len_r    <=          11'h000  ;
    end else if(sp_cnt_r == 13'd31) begin
        sp_nxt_r    <=          pkt_u16[15]  ;
        sp_len_r    <=          pkt_u16[10:0]  ;
    end

    //子报文计数复位标志,一定要等到计数32的时刻，确保能读到本报文长度
    //40为子报文头部长度, 8为wkc 长度减一
    assign  sp_cnt_reset = (sp_cnt_r ==(13'd40 + {sp_len_r,2'b00} +13'd7));

    //子报文加一条件
    //当子报文计数复位时刻，此时sp_nxt_r为0,则表示没有后续子报文，子报文计数不应该再增加
    reg sp_cnt_add_r        ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_cnt_add_r    <=          1'b0    ;
    end else if(sp_cnt_reset) begin
        sp_cnt_add_r    <=          sp_nxt_r    ;
    end else if(rxdv_posedge)
        sp_cnt_add_r    <=          1'b1    ;
    assign sp_cnt_add = sp_cnt_add_r;

    //子报文的状态位 , 计数40
    reg     [15:0]  sp_status_r        ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_status_r     <=          16'h000  ;
    end else if(sp_cnt_r == 13'd39) begin
        sp_status_r     <=          pkt_u16[15:0]  ;
    end

    //子报文数据，每4个clk产生一个8bit数据
    reg     [7:0]   sp_data_r           ;
    reg             sp_data_valid_r     ;   //高电平表示数据有效
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_data_r       <=          8'h00   ;
        sp_data_valid_r <=          1'b0    ;
    end else if((sp_cnt_r > 13'd40) & (sp_cnt_r < (13'd40 + {sp_len_r,2'b00}))) begin
        if(sp_cnt_r[1:0] == 2'b11) begin
            sp_data_r   <=          pkt_buf[7:0]    ;
            sp_data_valid_r <=      1'b1    ;   //数据有效会保持4个clk
        end else
            sp_data_valid_r <=      1'b0    ;
    end else
        sp_data_valid_r <=          1'b0    ;

    //每个数据对应的地址偏移
    reg     [10:0]  sp_data_addr_r      ;   //子报文数据地址偏移
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_data_addr_r  <=          11'h000 ;
    end else if(sp_cnt_r > 13'd44) begin    //子报文头接收完毕 40+4
        if(sp_cnt_r[1:0] == 2'b11) begin       
            sp_data_addr_r  <= sp_data_addr_r + 11'h01;
        end
    end else begin
        sp_data_addr_r  <=          11'h000    ;
    end 

    //子报文的wkc
    reg     [15:0]  sp_wkc_r            ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        sp_wkc_r        <=          16'h000  ;
    end else if(sp_cnt_reset) begin
        sp_wkc_r        <=          pkt_u16[15:0]  ;
    end
   
    //本从站的地址，当rxdv上升沿时候，从寄存器模块读出来更新此寄存器
    reg     [15:0]      slave_addr_r        ;
    //.... 
    //debug 代码
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n) begin
        slave_addr_r    <=          16'h000  ;
    end else begin
        slave_addr_r    <=          16'h0002  ; //测试代码
    end

    //命令分类，根据从站寻址分成4类ap,fp,b,l
    wire    cmd_ap_type ;
    wire    cmd_fp_type ;
    wire    cmd_b_type  ;   //ARMW(13), FRMW(14)归入这一类
    wire    cmd_l_type  ;
    assign cmd_ap_type = (sp_cmd_r == 8'h01) | (sp_cmd_r == 8'h02) | (sp_cmd_r == 8'h03) ;
    assign cmd_fp_type = (sp_cmd_r == 8'h04) | (sp_cmd_r == 8'h05) | (sp_cmd_r == 8'h06) ;
    assign cmd_b_type = (sp_cmd_r == 8'h07) | (sp_cmd_r == 8'h08) | (sp_cmd_r == 8'h09) | (sp_cmd_r == 8'h0d) | (sp_cmd_r == 8'h0e);
    assign cmd_l_type = (sp_cmd_r == 8'h0a) | (sp_cmd_r == 8'h0b) | (sp_cmd_r == 8'h0c);
    
    //从站地址是否相等或者为0
    wire fp_addr_eq;
    wire ap_addr_eq;
    assign fp_addr_eq = (sp_adp == slave_addr_r)    ;   //子报文地址是否等于从站地址
    assign ap_addr_eq = (sp_adp == 16'h0000)        ;   //子报文地址是否为0
 

    //FMMU设计为3个，一般一个用于输入，一个用于输出，第三个可以映射寄存器
    //FMMU设计，只支持按字节映射
    reg     [31:0]      fmmu0_laddr_r   ;   //逻辑地址0
    reg     [15:0]      fmmu0_paddr_r   ;   //物理地址0
    reg     [15:0]      fmmu0_len_r     ;   //映射地址长度
    reg                 fmmu0_enable    ;   //FMMU使能

    reg     [31:0]      fmmu1_laddr_r   ;   //逻辑地址1
    reg     [15:0]      fmmu1_paddr_r   ;   //物理地址1
    reg     [15:0]      fmmu1_len_r     ;   //映射物理长度
    reg                 fmmu1_enable    ;   //FMMU使能

    reg     [31:0]      fmmu2_laddr_r   ;   //逻辑地址2
    reg     [15:0]      fmmu2_paddr_r   ;   //物理地址2
    reg     [15:0]      fmmu2_len_r     ;   //映射物理长度
    reg                 fmmu2_enable    ;   //FMMU使能
    //逻辑地址产生
    //如果fmmu_enable有效
    // 如果sp_addr_r >= fmmu_laddr_r 并且 sp_addr_r - fmmu_laddr_r >= fmmu_len_r
    //则物理地址为 fmmu_paddr_r + (sp_addr_r - fmmu_laddr_r)
    


    //EtherCAT 64K空间的地址产生
    //如果是APxx指令并且sp_addr_r == 16'h00则操作本从站
    //如果是FPxx指令并且sp_addr_r == slave_addr_r，则是操作本从站
    //如果是Bxx，ARMW(13), FRMW(14) 则是操作本从站


    /* 
    //本模块EtherCAT地址空间共5K字节
    //0x0000 - 0x0fff :寄存器空间，共4K
    //0x1000 - 0x1400 :ram空间，共1K
    //ram空间，其中
    // 128B :邮箱通信
    // 128B :邮箱通信
    // 128B*3 : PDO输出
    // 128B*3 : PDO输入
    
    //读数据过程，当子报文长度后，开始启动读操作，此时已经得到数据地址，命令和数据长度
    //后续还有8个clk的状态位，此后开始传输数据，也就是说数据必须在8clk内读回
    //读数据指令 APRD(1), APRW(3), FPRD(4), FPRW(6), BRD(7), BRW(9), LRD(10), LRW(12), ARMW(13), FRMW(14)
    //对于APRD(1)和APRW(3)，ARMW(13)指令，当sp_addr_r 为0时候们可以读
    //对于FPRD(4), FPRW(6)，FRMW(14)指令，当sp_addr_r 等于从站地址时，
    //对于BRD(7), BRW(9)指令，直接读
    
    //本从站的地址，当rxdv上升沿时候，从寄存器模块读出来更新此寄存器
    reg     [15:0]      slave_addr_r        ;
    wire                ap_sel              ;   //APXX指令
    wire                fp_sel              ;   //FPXX指令
    wire                b_sel               ;   //BXX指令
    wire                l_sel               ;   //LXX指令
    wire                mw_sel              ;   //ARMW,FRMW指令
    assign ap_sel = (sp_addr_r == 16'h00) & ((sp_cmd_r == 8'h01) | (sp_cmd_r == 8'h02) | (sp_cmd_r == 8'h03) | (sp_cmd_r == 8'h0d));
    assign fp_sel = (sp_addr_r == slave_addr_r) & ((sp_cmd_r == 8'h04) | (sp_cmd_r == 8'h05) | (sp_cmd_r == 8'h06) | (sp_cmd_r == 8'h0e));
    assign b_sel = (sp_cmd_r == 8'h07) | (sp_cmd_r == 8'h08) | (sp_cmd_r == 8'h09);
    assign l_sel = (sp_cmd_r == 8'h0a) | (sp_cmd_r == 8'h0b) | (sp_cmd_r == 8'h0c);
    assign mw_sel = ((sp_addr_r != 16'h00) & (sp_cmd_r == 8'h0d)) | ((sp_addr_r != slave_addr_r) & (sp_cmd_r == 8'h0e));

    //在AP寻址，FP寻址，B寻址下，ado大于16'h0fff为ram地址 反之为寄存器地址
    wire        ecat_regs_sel ;
    assign ecat_regs_sel = (sp_ado < 16'h1000) ;
    wire        ecat_dram_sel   ;
    assign ecat_dram_sel = !ecat_regs_sel;



    


    //EtherCAT寄存器模块
    ecat_regs u_ecat_regs
    (
        //端口0 
        .clk_a          (           sys_clk         ),
        .addr_a         (),
        .we_a           (),
        .data_a         (),
        .q_a            (),
        
        //端口1
        .clk_b          (),
        .addr_b         (),
        .we_b           (),
        .data_b         (),
        .q_b            ()
    );


    //顺序寻址报文经过从站时，每个子报文地址都要加一
    */

    //*******************************************************************仿真测试
    /* synthesis translate_off */
    always_ff @(posedge sys_clk)
        begin
            case(rxd_cnt_r)
                13'd24: begin  //24个时钟后可以打印目的MAC地址
                    $display("DMAC:  %h:%h:%h:%h:%h:%h",pkt_buf[47:40],pkt_buf[39:32],pkt_buf[31:24],pkt_buf[23:16],pkt_buf[15:8],pkt_buf[7:0]);
                end
                13'd48: begin   //打印原MAC地址 24+24
                    $display("SMAC:  %h:%h:%h:%h:%h:%h",pkt_buf[47:40],pkt_buf[39:32],pkt_buf[31:24],pkt_buf[23:16],pkt_buf[15:8],pkt_buf[7:0]);
                end
                13'd57:begin    //打印协议类型 24+24+8
                    $display("proto:  %h",ecat_proto_r);
                end
                13'd65:begin //打印ECAT帧头
                    $display("ECAT Frame Length :%d Frame type :%d\n\n子报文开始\n",ecat_fr_len, ecat_fr_type);
                end
            endcase
        end
    
    //子报文信息打印
    always_ff @(posedge sys_clk)
        begin
            case(sp_cnt_r)
                13'd1:$display("上一子报文 WKC ：%h",sp_wkc_r);
                13'd8: begin  //8个时钟后可以打印命令和索引
                    $display("-----------------> CMD:%h  index :%h ",sp_cmd_r, sp_ind_r);
                    case(sp_cmd_r)
                        8'h0:$display("NOP 空指令 命令");
                        8'h1:$display("APRD 顺序读数据 命令");
                        8'h2:$display("APWR 顺序写数据 命令");
                        8'h3:$display("APRW 顺序交换数据 命令");
                        8'h4:$display("FPRD 设置读数据 命令");
                        8'h5:$display("FPWR 设置写数据 命令");
                        8'h6:$display("FPRW 设置交换数据 命令");
                        8'h7:$display("BRD 广播读数据 命令");
                        8'h8:$display("BWR 广播写数据 命令");
                        8'h9:$display("BRW 广播交换数据 命令");
                        8'ha:$display("LRD 逻辑读数据 命令");
                        8'hb:$display("LWR 逻辑写数据 命令");
                        8'hc:$display("LRW 逻辑交换数据 命令");
                        8'hd:$display("ARMW 顺序单读多写数据 命令");
                        8'he:$display("FRMW 设置单读多写数据 命令");
                        default:
                            $display("错误 不支持的命令命令:%h",sp_cmd_r);
                    endcase
                end
                13'd24: begin   //打印地址 7+16
                    $display("ADDR:  %h",sp_addr_r);
                    $display("站点地址:%h 站内偏移: %h", sp_adp, sp_ado);
                end
                13'd32:begin    //长度 7+16+8
                    $display("LEN:  %h",sp_len_r);
                end
                13'd40:begin //状态 7+16+8+8
                    $display("子报文状态 :%h ",sp_status_r);
                end
            endcase
        end

    //打印子报文数据
    always_ff @(posedge sys_clk)
    begin
        if(sp_data_valid_r)
            $display("子报文 偏移地址 %h 数据 :%h",sp_data_addr_r,sp_data_r);
    end
    /* synthesis translate_on */
    //*******************************************************************

    //发送数据计数和接收数据计数之间关系
    //接收数据计数=数据长度+4字节CRC32长度
    //发送长度计数=8字节同步区域+数据长度， CRC32自己计算产生

    //发送数据计数
    reg     [12:0]      txd_cnt_r   ;
    always_ff @(posedge sys_clk or negedge sys_reset_n)
    if(!sys_reset_n)begin
        txd_cnt_r       <=      13'h000 ;
    end else if(~sfd_r)
        txd_cnt_r       <=      13'h000 ;
    else if(txd_cnt_r < (rxd_cnt_r + 13'd32 )) //接收长度包括CRC + 同步区域长度
        txd_cnt_r       <=      txd_cnt_r   + 13'h001;   


    //测试
    assign txen = 1'b0;
    assign txd = 2'b00;
endmodule
